// raymarched @party logo
uniform mat4 modelMtx;
uniform mat4 viewMtx;
uniform mat4 projMtx;

uniform float time;

// https://www.shadertoy.com/view/Mll3W2
#define PARTY_ON

#define t (3.*time)
float party = 0.;

// math
const float PI = 3.14159265359;
const float DEG_TO_RAD = PI / 180.0;
mat3 rotationX(float r) {
    float ct=cos(r), st=sin(r);
    return mat3(1., 0.,  0.,  0., ct, -st,  0., st,  ct);}
mat3 rotationY(float r) {
    float ct=cos(r), st=sin(r);
    return mat3(ct, 0., st,  0., 1., 0.,  -st, 0., ct);}
mat3 rotationZ(float r) {
    float ct=cos(r), st=sin(r);
    return mat3(ct, -st, 0.,  st, ct, 0.,  0., 0., 1.);}
mat3 rotationXY(vec2 angle) {
    vec2 c = cos(angle);
    vec2 s = sin(angle);
    return mat3(
        c.y    ,  0.0, -s.y,
        s.y*s.x,  c.x,  c.y*s.x,
        s.y*c.x, -s.x,  c.y*c.x);
}


// libiq

// exponential smooth min (k = 32);
float smine( float a, float b, float k )
{
    float res = exp( -k*a ) + exp( -k*b );
    return -log( res )/k;
}

// polynomial smooth min (k = 0.1);
float smin( float a, float b, float k )
{
    float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
    return mix( b, a, h ) - k*h*(1.0-h);
}

////////////// DISTANCE FUNCTIONS
//
// Primitives from http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
//
float udBox( vec3 p, vec3 b )
{
  return length(max(abs(p)-b,0.0));
}
float sdBox( vec3 p, vec3 b )
{
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) +
         length(max(d,0.0));
}
float udRoundBox( vec3 p, vec3 b, float r )
{
    return length(max(abs(p)-b,0.0))-r;
}
float sdPlane( vec3 p, vec4 n )
{
    // n must be normalized
    return dot(p,n.xyz) + n.w;
}

//
// Composites
//
float rollprism(vec3 pos)
{
    return max(
        udRoundBox(pos, vec3(2.,1.4,5.), 3.5),
        -sdPlane(pos, vec4(normalize(vec3(-1.,0.,0.)),3.))
        );
}

float rollprism2(vec3 pos)
{
    return max(
        udRoundBox(pos, vec3(2.,2.,5.), 7.5),
        -udRoundBox(pos, vec3(2.,2.,15.), 5.5)
        );
}

float atsign(vec3 pos)
{
    float d = min(
        rollprism(pos-vec3(1.,0.,0.)),
        rollprism2(pos)
        );
    d = smin(d,
             sdBox(pos-vec3(4.2,-3.5,0.), vec3(3.5,1.4,5.))
            ,1.);    
    d = max(d, -sdBox(pos-vec3(6.,-8.4,0.), vec3(6.,3.5,12.))); // chop horiz
    
    // chop off front and back
    float w = 0.;//1.+sin(t);
    d = max(d, -sdPlane(pos                 , vec4(vec3(0.,0.,-1.),0.)));
    d = max(d, -sdPlane(pos-vec3(0.,0.,-1.5-w), vec4(vec3(0.,0.,1.),0.)));
    return d;
}

float attail(vec3 pos)
{
    float s = -0.+0.5*pow((1.+0.1*pos.x),1.7);
    return sdBox(pos, vec3(5.,0.8+s,.6+s));
}

float at(vec3 pos)
{
    // dance
    float p = 0.02*party;
    pos = rotationY(p*pos.y*sin(3.*t)) * pos;
    pos = rotationX(p*pos.y*sin(5.*t)) * pos;
    
    return min(
        atsign(pos),
        attail(
            rotationY(-.03*(pos.x+7.)) *
            (pos-vec3(3.,-8.5,.0)))
        );
}

float DE_atlogo( vec3 pos )
{
    mat3 rotmtx = mat3(modelMtx);//1.);//getMouseRotMtx();
    pos = rotmtx * pos;
    float d2 = 9999.;
    return min(d2, at(pos));
}


//
// lighting and shading
//
vec3 shading( vec3 v, vec3 n, vec3 eye ) {
    float shininess = 16.0;
    vec3 ev = normalize( v - eye );
    vec3 ref_ev = reflect( ev, n ); 
    vec3 final = vec3( 0.0 );
    // disco light
    {
        vec3 light_pos   = vec3( 1.0, 10.0, 30.0 );
        float p = party;
        vec3 light_color = vec3(p*sin(3.*t), p*sin(3.3*t), p*sin(4.*t));
        vec3 vl = normalize( light_pos - v );
    
        float diffuse  = 0.;//max( 0.0, dot( vl, n ) );
        float specular = max( 0.0, dot( vl, ref_ev ) );
        specular = pow( specular, shininess );
        
        final += light_color * ( diffuse + specular ); 
    }
    
    // white light
    {
        vec3 light_pos   = vec3( 10.0, 20.0, -10.0 );
        //vec3 light_pos   = vec3( -20.0, -20.0, -20.0 );
        vec3 light_color = vec3( 1.);//0.3, 0.7, 1.0 );
        vec3 vl = normalize( light_pos - v );
    
        shininess = 8.;
        float diffuse  = 0.;//max( 0.0, dot( vl, n ) );
        float specular = max( 0.0, dot( vl, ref_ev ) );
        specular = pow( specular, shininess );
        
        final += light_color * ( diffuse + specular ); 
    }

    return final;
}

// get gradient in the world
vec3 gradient( vec3 pos ) {
    const float grad_step = 0.31;
    const vec3 dx = vec3( grad_step, 0.0, 0.0 );
    const vec3 dy = vec3( 0.0, grad_step, 0.0 );
    const vec3 dz = vec3( 0.0, 0.0, grad_step );
    return normalize (
        vec3(
            DE_atlogo( pos + dx ) - DE_atlogo( pos - dx ),
            DE_atlogo( pos + dy ) - DE_atlogo( pos - dy ),
            DE_atlogo( pos + dz ) - DE_atlogo( pos - dz )   
        )
    );
}

// ray marching
float ray_marching( vec3 origin, vec3 dir, float start, float end ) {
    const int max_iterations = 255;
    const float stop_threshold = 0.001;
    float depth = start;
    for ( int i = 0; i < max_iterations; i++ ) {
        float dist = DE_atlogo( origin + dir * depth );
        if ( dist < stop_threshold ) {
            return depth;
        }
        depth += dist;
        if ( depth >= end) {
            return end;
        }
    }
    return end;
}

// get ray direction from pixel position
vec3 ray_dir( float fov, vec2 xy ) {
    float cot_half_fov = tan( ( 90.0 - fov * 0.5 ) * DEG_TO_RAD );  
    float z = 1. * cot_half_fov;
    
    return normalize( vec3( xy, -z ) );
}


vec3 getSceneColor_atlogo( in vec3 ro, in vec3 rd )
{
    const float clip_far = 100.0;
    float depth = ray_marching( ro, rd, -10.0, clip_far );
    if ( depth >= clip_far ) {
        return vec3(.5);
    }
    vec3 pos = ro + rd * depth;
    vec3 n = gradient( pos );
    return shading( pos, n, ro );
}


//
// The cube
//
mat3 getCubeMtx()
{
    return rotationY(.8+party*pow(abs(sin(PI*2.*time)),5.)) * rotationX(.3);
}

float DE_cube( vec3 pos )
{
    pos = getCubeMtx() * mat3(modelMtx) * pos;
    return udRoundBox(pos, vec3(1.5), .15);
}

vec3 shade_cube(vec3 v, vec3 n, vec3 ntx, vec3 eye) {
    vec3 ev = normalize(v - eye);
    vec3 final = vec3(0.);
    vec3 light_pos = vec3(-10.,20.,40.);
    vec3 vl = normalize(light_pos - v);
    float diffuse = max(0.0, dot( vl, n ));
    final += 1.3 * diffuse; 
    // transform normals with the cube to find flat faces/edges
    float px = abs(dot(ntx,vec3(1.,0.,0.)));
    float py = abs(dot(ntx,vec3(0.,1.,0.)));
    float pz = abs(dot(ntx,vec3(0.,0.,1.)));
    float p = max(px,max(py,pz));
    final *= smoothstep(0.9,1.,length(p));
    return final;
}

vec3 grad_cube(vec3 pos) {
    const float gs = 0.02;
    const vec3 dx = vec3(gs, 0., 0.);
    const vec3 dy = vec3(0., gs, 0.);
    const vec3 dz = vec3(0., 0., gs);
    return normalize( vec3(
            DE_cube(pos + dx) - DE_cube(pos - dx),
            DE_cube(pos + dy) - DE_cube(pos - dy),
            DE_cube(pos + dz) - DE_cube(pos - dz)   
        ));
}

float raymarch_cube(vec3 origin, vec3 dir, float start, float end) {
    const int max_iterations = 64;
    const float stop_threshold = 0.01;
    float depth = start;
    for (int i=0; i<max_iterations; i++) {
        float dist = DE_cube(origin + dir*depth);
        if (dist < stop_threshold) return depth;
        depth += dist;
        if (depth >= end) return end;
    }
    return end;
}

vec3 getCubeColor(in vec3 ro, in vec3 rd) {
    const float clip_far = 100.0;
    float depth = raymarch_cube(ro, rd, 0., clip_far);
    if ( depth >= clip_far )
        return getSceneColor_atlogo(ro,rd);
    vec3 pos = ro + rd * depth;
    vec3 ne = grad_cube(pos);
    vec3 ntx = getCubeMtx() * ne;
    return shade_cube(pos, ne, ntx, ro);
}

///////////////////////////////////////////////////////////////////////////////
// Patch in the Rift's heading to raymarch shader writing out color and depth.
// http://blog.hvidtfeldts.net/

// Translate the origin to the camera's location in world space.
vec3 getEyePoint(mat4 mvmtx)
{
    vec3 ro = -mvmtx[3].xyz;
    return ro;
}

// Construct the usual eye ray frustum oriented down the negative z axis.
// http://antongerdelan.net/opengl/raycasting.html
vec3 getRayDirection(vec2 uv)
{
    vec4 ray_clip = vec4(uv.x, uv.y, -1., 1.);
    vec4 ray_eye = inverse(projMtx) * ray_clip;
    return normalize(vec3(ray_eye.x, ray_eye.y, -1.));
}

void main()
{
#ifdef PARTY_ON
    party = 1.;
#endif

    vec2 uv11 = uv * 2.0 - vec2(1.0);
    vec3 ro = getEyePoint(viewMtx);
    vec3 rd = getRayDirection(uv11);

    ro.z += 10.;
    ro *= mat3(viewMtx);
    rd *= mat3(viewMtx);

    fragColor = vec4( getCubeColor(ro, rd), 1.0 );
}
